using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace StreetlightGames.XnaEngine
{
    /// <summary>
    /// Component for drawing a 3D model.  Also provides some basic physics.
    /// </summary>
    public class ModelObject : DrawableGameComponent
    {
        /// <summary>Gets or Sets the name of the model.  This is loaded automatically by the component model when LoadContent is called.</summary>
        public string ModelName
        {
            get { return _modelName; }
            set { _modelName = value; }
        }
        private string _modelName;

        /// <summary>Gets the Model associated with the component.</summary>
        public Model Model
        {
            get { return _model; }
            protected set { _model = value; }
        }
        private Model _model;

        /// <summary>Gets or sets the Position of the model in 3D space.</summary>
        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }
        private Vector3 _position;

        /// <summary>Gets or sets the Rotation of the model on all 3 axes (X = pitch, Y = yaw, Z = roll).</summary>
        public Vector3 Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }
        private Vector3 _rotation;

        /// <summary>Gets or sets the Scale of the model.</summary>
        public float Scale
        {
            get { return _scale; }
            set { _scale = value; }
        }
        private float _scale = 1.0f;

        /// <summary>Gets or sets the Velocity as a 3D vector.</summary>
        public Vector3 Velocity
        {
            get { return _velocity; }
            set { _velocity = value; }
        }
        private Vector3 _velocity = Vector3.Zero;

        /// <summary>Gets or sets an indicator of whether the object is "Alive".  If true, the position
        /// is updated during the Update method based on the velocity and the object is drawn during the
        /// Draw method. If false, the object is not updated or drawn  This also sets the Visible property 
        /// of the base DrawableGameComponent.</summary>
        public bool IsAlive
        {
            get { return _isAlive; }
            set
            {
                _isAlive = value;
                Visible = value;
            }
        }
        private bool _isAlive;

        /// <summary>Gets or sets the Camera used to render the model.</summary>
        public virtual ICamera Camera
        {
            get { return _camera; }
            set { _camera = value; }
        }
        private ICamera _camera;

        /// <summary>
        /// Initializes a new instance of the ModelObject class for the specified Game.
        /// </summary>
        /// <param name="game">The Game the component sould be attached to</param>
        public ModelObject(Game game)
            : base(game)
        {
            Visible = false;
        }

        /// <summary>
        /// Initializes a new instance of the ModelObject class for the specified Game and model.
        /// </summary>
        /// <param name="game">The Game the component sould be attached to</param>
        /// <param name="modelName">The name of the model to be loaded</param>
        public ModelObject(Game game, string modelName)
            : base(game)
        {
            ModelName = modelName;
            Visible = false;
        }

        /// <summary>
        /// Initializes a new instance of the ModelObject class for the specified Game and model.
        /// </summary>
        /// <param name="game">The Game the component sould be attached to</param>
        /// <param name="model">Model to be rendered by the component</param>
        public ModelObject(Game game, Model model)
            : base(game)
        {
            _model = model;
            Visible = false;
        }

        /// <summary>
        /// Loads the model specified by the ModelName property.
        /// </summary>
        protected override void LoadContent()
        {
            _model = Game.Content.Load<Model>(ModelName);
            base.LoadContent();
        }

        /// <summary>
        /// Draws the 3D model using the assigned Camera.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to Draw</param>
        public override void Draw(GameTime gameTime)
        {
            foreach (ModelMesh mesh in Model.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;

                    effect.World = Matrix.CreateFromYawPitchRoll(
                        Rotation.Y,
                        Rotation.X,
                        Rotation.Z) *
                        Matrix.CreateScale(Scale) *
                        Matrix.CreateTranslation(Position);
                    effect.Projection = Camera.ProjectionMatrix;
                    effect.View = Camera.ViewMatrix;
                }
                mesh.Draw();
            }
            base.Draw(gameTime);
        }

        /// <summary>
        /// Updates the position of the object based on the current velocity.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to Update</param>
        public override void Update(GameTime gameTime)
        {
            if (IsAlive && Velocity != Vector3.Zero)
            {
                Position += Velocity * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            base.Update(gameTime);
        }

        public void RotateX(float amount)
        {
            _rotation.X += amount;
        }

        public void RotateY(float amount)
        {
            _rotation.Y += amount;
        }

        public void RotateZ(float amount)
        {
            _rotation.Z += amount;
        }

        public void ClampX(float min, float max)
        {
            _rotation.X = MathHelper.Clamp(_rotation.X, min, max);
        }

        public void ClampY(float min, float max)
        {
            _rotation.Y = MathHelper.Clamp(_rotation.Y, min, max);
        }

        public void ClampZ(float min, float max)
        {
            _rotation.Z = MathHelper.Clamp(_rotation.Z, min, max);
        }

        private BoundingSphere GetCurrentBoundingSphere(ModelMesh mesh)
        {
            BoundingSphere boundingSphere = mesh.BoundingSphere;
            boundingSphere.Center = Position;
            boundingSphere.Radius *= Scale;
            return boundingSphere;
        }

        public bool SphereIntersects(ModelObject targetObject)
        {
            foreach (ModelMesh mesh in Model.Meshes)
            {
                BoundingSphere boundingSphere = GetCurrentBoundingSphere(mesh);
                foreach (ModelMesh targetMesh in targetObject.Model.Meshes)
                {
                    BoundingSphere targetSphere = targetObject.GetCurrentBoundingSphere(targetMesh);
                    if (boundingSphere.Intersects(targetSphere))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    }
}
